package seteam12;

import java.text.DecimalFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Observable;

import javax.swing.text.NumberFormatter;

/**
 * The model in MVC, and the context of the strategy pattern
 * 
 * 
 */
public class SudokuBoard extends Observable {

    private int size = 9;

    private Solver solver;

    private int[][] board;
    
    private int dif;

    private ArrayList<Cell> fixedCells;

    private IGenerator generator;

    public SudokuBoard() {
	board = new int[size][size];
	// generator = new FixedSudokusGenerator();
	generator = new Generator();
    }

    /**
     * Generates a new board.
     */
    public void newBoard() {
	board = new int[size][size];
	fixedCells = new ArrayList<Cell>();
	generator.generateFill(this, dif);

	for (int i = 0; i < size; i++) {
	    for (int j = 0; j < size; j++) {
		if (board[i][j] != 0) {
		    fixedCells.add(new Cell(i, j, board[i][j], true));
		}
	    }
	}

	setChanged();
	notifyObservers(new SudokuBoardChangeEvent());
    }

    /**
     * @return A list of all cells that were on the board originally. These are
     *         the cells the user is not allowed to change.
     */
    public List<Cell> getFixedCells() {
	return new ArrayList<Cell>(fixedCells); // return copy
    }

    public void setSize(int size) {
	throw new UnsupportedOperationException(
		"Only size 9 possible at the moment!");
    }

    public int getSize() {
	return size;
    }

    /**
     * Checks if every cell in the board is filled. As no illegal states are
     * possible, this means that the sudoku has been solved.
     * 
     * @return
     */
    public boolean isSolved() {
	for (int i = 0; i < size; i++) {
	    for (int j = 0; j < size; j++) {
		if (board[i][j] == 0)
		    return false;
	    }
	}
	return true;
    }

    public boolean solve() {
	if (isSolved())
	    return true;
	boolean solvable = solver.solve(this);
	if (solvable) {
	    setChanged();
	    notifyObservers(new SudokuBoardChangeEvent());
	}
	return solvable;
    }

    /**
     * Clears all values of the board instead of the ones given at the start (=
     * the fixed values).
     */
    public void reset() {
	board = new int[size][size];
	for (Cell cell : getFixedCells()) {
	    board[cell.line][cell.column] = cell.value;
	}
	setChanged();
	notifyObservers(new SudokuBoardChangeEvent());
    }

    public void setSolver(Solver newSolver) {
	this.solver = newSolver;
    }
    
    //set the chosen difficulty from the SudokuView for the game
    public void setDifficulty(Integer dif) {
    	this.dif = dif;
    	System.out.println(dif);
    }

    /**
     * Prints the board to console.
     */
    public void printBoard() {
	assert Math.sqrt(size) * Math.sqrt(size) == size : "Size needs to be a square value!";
	String currentNum;
	NumberFormatter nf;
	if (size < 10) {
	    nf = new NumberFormatter(new DecimalFormat("0"));
	} else {
	    nf = new NumberFormatter(new DecimalFormat("00"));
	}

	try {
	    int numberWidth = nf.valueToString(size).length();
	    String lineSeparator = "";
	    int nextSeperatorColumn = Double.valueOf(Math.sqrt(size))
		    .intValue() - 1;
	    int nextSeperatorLine = nextSeperatorColumn;
	    String unsetString = "";
	    for (int i = 0; i < numberWidth; i++) {
		unsetString += "X";
	    }

	    for (int i = 0; i < size * numberWidth + (size + 1)
		    + Math.sqrt(size) - 1; i++) {
		lineSeparator += "-";
	    }
	    for (int i = 0; i < size; i++) {
		currentNum = board[i][0] > 0 ? nf.valueToString(board[i][0])
			: unsetString;
		System.out.print("|" + currentNum + "|");
		nextSeperatorColumn = Double.valueOf(Math.sqrt(size))
			.intValue() - 1;
		for (int j = 1; j < size; j++) {
		    currentNum = board[i][j] > 0 ? nf
			    .valueToString(board[i][j]) : unsetString;
		    System.out.print(currentNum + "|");
		    if (j == nextSeperatorColumn && j != size - 1) {
			System.out.print("|");
			nextSeperatorColumn += Double.valueOf(Math.sqrt(size))
				.intValue();
		    }
		}
		System.out.print("\n");
		if (i == nextSeperatorLine && i != size - 1) {
		    System.out.println(lineSeparator);
		    nextSeperatorLine += Double.valueOf(Math.sqrt(size))
			    .intValue();
		}
	    }
	    System.out.print("\n");
	} catch (ParseException e) {
	    System.err.println("Unexpected NumberFormat Exception: "
		    + e.getLocalizedMessage());
	}
    }

    /**
     * Sets the sudoku's value at the given cell iff it is legal to do so.
     * 
     * @param value
     * @param line
     * @param column
     * @return true if the value is legal (locally), false otherwise
     */
    public boolean setValue(int value, int line, int column) {
	// assert this.isLegal(); //TODO: implement global board validity check?

	if (fixedCells.contains(new Cell(line, column))) {
	    return false;
	}

	if (value == 0) {
	    board[line][column] = 0;
	    setChanged();
	    notifyObservers(new SudokuBoardChangeEvent(new Cell(line, column)));
	}

	int origValue = board[line][column];
	board[line][column] = value; // set the value first, makes checking
				     // easier as the special case that a value
				     // is "set again" is no special case then
	boolean encounteredValue = false;

	// first check current row
	for (int i = 0; i < 9; i++) {
	    if (board[line][i] == value) {
		if (encounteredValue) {
		    board[line][column] = origValue;
		    return false;
		}
		encounteredValue = true;
	    }
	}

	// check column
	encounteredValue = false;
	for (int i = 0; i < 9; i++) {
	    if (board[i][column] == value) {
		if (encounteredValue) {
		    board[line][column] = origValue;
		    return false;
		}
		encounteredValue = true;
	    }
	}

	// check box
	assert Math.sqrt(size) * Math.sqrt(size) == size : "Size is no quadratic number.";
	int boxSize = Double.valueOf(Math.sqrt(size)).intValue();
	int boxLineStart = line - line % boxSize;
	int boxColumnStart = column - column & boxSize;

	encounteredValue = false;
	for (int i = boxLineStart; i < boxLineStart + boxSize; i++) {
	    for (int j = boxColumnStart; j < boxColumnStart + boxSize; j++) {
		if (board[i][j] == value) {
		    if (encounteredValue) {
			board[line][column] = origValue;
			return false;
		    }
		    encounteredValue = true;
		}
	    }
	}

	setChanged();
	notifyObservers(new SudokuBoardChangeEvent(
		new Cell(line, column, value)));

	return true;
    }

    /**
     * @param line
     * @param column
     * @return The value in the given cell, or 0 (i.e. zero) if it has not yet
     *         been set.
     */
    public int getValue(int line, int column) {
	return board[line][column];
    }

    /**
     * @return The sudoku in a two dimensional array representation. Changes to
     *         the array will not be reflected on the board.
     */
    public int[][] getValues() {
	// return deep copy of the board
	int[][] boardCopy = new int[size][];
	for (int i = 0; i < size; i++) {
	    boardCopy[i] = Arrays.copyOf(board[i], size);
	}
	return boardCopy;
    }

    /**
     * Sets the sudokus values in a single step to the given grid. This method
     * only performs a dimension check, but no validity check.
     * 
     * @param valueGrid
     *            The two dimensional grid filled with values.
     * @throws IllegalArgumentException
     *             If the dimensions of the array do not fit the sudoku size.
     */
    public void setValues(int[][] valueGrid) throws IllegalArgumentException {
	if (valueGrid.length != size || valueGrid[0].length != size) {
	    throw new IllegalArgumentException(
		    "The given valueGrid must be of size " + size + "x" + size
			    + "!");
	}
	board = valueGrid;
	setChanged();
	notifyObservers(new SudokuBoardChangeEvent());
    }

}
